
/*
 * Copyright (C) Yichun Zhang (agentzh)
 */


#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"


#if (NGX_HTTP_SSL)


#include "ngx_http_lua_common.h"


#ifndef NGX_LUA_NO_FFI_API

#ifdef NGX_HTTP_LUA_USE_OCSP
static int ngx_http_lua_ssl_empty_status_callback(ngx_ssl_conn_t *ssl_conn,
                                                  void           *data);
#endif


int
ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain(const char *chain_data,
                                                       size_t      chain_len,
                                                       unsigned char *out,
                                                       size_t        *out_size,
                                                       char         **err)
{
#ifndef NGX_HTTP_LUA_USE_OCSP

    *err = "no OCSP support";
    return NGX_ERROR;

#else

    int    rc  = NGX_OK;
    BIO   *bio = NULL;
    char  *s;
    X509  *cert = NULL, *issuer = NULL;
    size_t len;
    STACK_OF(OPENSSL_STRING) *aia = NULL;

    /* certificate */

    bio = BIO_new_mem_buf((char *)chain_data, chain_len);
    if (bio == NULL)
    {
        *err = "BIO_new_mem_buf() failed";
        rc   = NGX_ERROR;
        goto done;
    }

    cert = d2i_X509_bio(bio, NULL);
    if (cert == NULL)
    {
        *err = "d2i_X509_bio() failed";
        rc   = NGX_ERROR;
        goto done;
    }

    /* responder */

    aia = X509_get1_ocsp(cert);
    if (aia == NULL)
    {
        rc = NGX_DECLINED;
        goto done;
    }

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
    s = sk_OPENSSL_STRING_value(aia, 0);
#else
    s = sk_value(aia, 0);
#endif
    if (s == NULL)
    {
        rc = NGX_DECLINED;
        goto done;
    }

    len = ngx_strlen(s);
    if (len > *out_size)
    {
        len = *out_size;
        rc  = NGX_BUSY;
    }
    else
    {
        rc        = NGX_OK;
        *out_size = len;
    }

    ngx_memcpy(out, s, len);

    X509_email_free(aia);
    aia = NULL;

    /* issuer */

    if (BIO_eof(bio))
    {
        *err = "no issuer certificate in chain";
        rc   = NGX_ERROR;
        goto done;
    }

    issuer = d2i_X509_bio(bio, NULL);
    if (issuer == NULL)
    {
        *err = "d2i_X509_bio() failed";
        rc   = NGX_ERROR;
        goto done;
    }

    if (X509_check_issued(issuer, cert) != X509_V_OK)
    {
        *err = "issuer certificate not next to leaf";
        rc   = NGX_ERROR;
        goto done;
    }

    X509_free(issuer);
    X509_free(cert);
    BIO_free(bio);

    return rc;

done:

    if (aia)
    {
        X509_email_free(aia);
    }

    if (issuer)
    {
        X509_free(issuer);
    }

    if (cert)
    {
        X509_free(cert);
    }

    if (bio)
    {
        BIO_free(bio);
    }

    if (rc == NGX_ERROR)
    {
        ERR_clear_error();
    }

    return rc;

#endif /* NGX_HTTP_LUA_USE_OCSP */
}


int
ngx_http_lua_ffi_ssl_create_ocsp_request(const char *chain_data,
                                         size_t chain_len, unsigned char *out,
                                         size_t *out_size, char **err)
{
#ifndef NGX_HTTP_LUA_USE_OCSP

    *err = "no OCSP support";
    return NGX_ERROR;

#else

    int rc = NGX_ERROR;
    BIO *bio = NULL;
    X509 *cert = NULL, *issuer = NULL;
    size_t len;
    OCSP_CERTID *id;
    OCSP_REQUEST *ocsp = NULL;

    /* certificate */

    bio = BIO_new_mem_buf((char *)chain_data, chain_len);
    if (bio == NULL)
    {
        *err = "BIO_new_mem_buf() failed";
        goto failed;
    }

    cert = d2i_X509_bio(bio, NULL);
    if (cert == NULL)
    {
        *err = "d2i_X509_bio() failed";
        goto failed;
    }

    if (BIO_eof(bio))
    {
        *err = "no issuer certificate in chain";
        goto failed;
    }

    issuer = d2i_X509_bio(bio, NULL);
    if (issuer == NULL)
    {
        *err = "d2i_X509_bio() failed";
        goto failed;
    }

    ocsp = OCSP_REQUEST_new();
    if (ocsp == NULL)
    {
        *err = "OCSP_REQUEST_new() failed";
        goto failed;
    }

    id = OCSP_cert_to_id(NULL, cert, issuer);
    if (id == NULL)
    {
        *err = "OCSP_cert_to_id() failed";
        goto failed;
    }

    if (OCSP_request_add0_id(ocsp, id) == NULL)
    {
        *err = "OCSP_request_add0_id() failed";
        goto failed;
    }

    len = i2d_OCSP_REQUEST(ocsp, NULL);
    if (len <= 0)
    {
        *err = "i2d_OCSP_REQUEST() failed";
        goto failed;
    }

    if (len > *out_size)
    {
        *err = "output buffer too small";
        *out_size = len;
        rc = NGX_BUSY;
        goto failed;
    }

    len = i2d_OCSP_REQUEST(ocsp, &out);
    if (len <= 0)
    {
        *err = "i2d_OCSP_REQUEST() failed";
        goto failed;
    }

    *out_size = len;

    OCSP_REQUEST_free(ocsp);
    X509_free(issuer);
    X509_free(cert);
    BIO_free(bio);

    return NGX_OK;

failed:

    if (ocsp)
    {
        OCSP_REQUEST_free(ocsp);
    }

    if (issuer)
    {
        X509_free(issuer);
    }

    if (cert)
    {
        X509_free(cert);
    }

    if (bio)
    {
        BIO_free(bio);
    }

    ERR_clear_error();

    return rc;

#endif /* NGX_HTTP_LUA_USE_OCSP */
}


int
ngx_http_lua_ffi_ssl_validate_ocsp_response(const u_char *resp, size_t resp_len,
                                            const char *chain_data,
                                            size_t chain_len, u_char *errbuf,
                                            size_t *errbuf_size)
{
#ifndef NGX_HTTP_LUA_USE_OCSP

    *errbuf_size =
        ngx_snprintf(errbuf, *errbuf_size, "no OCSP support") - errbuf;
    return NGX_ERROR;

#else

    int n;
    BIO *bio = NULL;
    X509 *cert = NULL, *issuer = NULL;
    OCSP_CERTID *id = NULL;
    OCSP_RESPONSE *ocsp = NULL;
    OCSP_BASICRESP *basic = NULL;
    STACK_OF(X509) *chain = NULL;
    ASN1_GENERALIZEDTIME *thisupdate, *nextupdate;

    ocsp = d2i_OCSP_RESPONSE(NULL, &resp, resp_len);
    if (ocsp == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "d2i_OCSP_RESPONSE() failed")
            - errbuf;
        goto error;
    }

    n = OCSP_response_status(ocsp);

    if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL)
    {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "OCSP response not successful (%d: %s)", n,
                                    OCSP_response_status_str(n))
                       - errbuf;
        goto error;
    }

    basic = OCSP_response_get1_basic(ocsp);
    if (basic == NULL)
    {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "OCSP_response_get1_basic() failed")
                       - errbuf;
        goto error;
    }

    /* get issuer certificate from chain */

    bio = BIO_new_mem_buf((char *)chain_data, chain_len);
    if (bio == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "BIO_new_mem_buf() failed")
            - errbuf;
        goto error;
    }

    cert = d2i_X509_bio(bio, NULL);
    if (cert == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "d2i_X509_bio() failed")
            - errbuf;
        goto error;
    }

    if (BIO_eof(bio))
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "no issuer certificate in chain")
            - errbuf;
        goto error;
    }

    issuer = d2i_X509_bio(bio, NULL);
    if (issuer == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "d2i_X509_bio() failed")
            - errbuf;
        goto error;
    }

    chain = sk_X509_new_null();
    if (chain == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "sk_X509_new_null() failed")
            - errbuf;
        goto error;
    }

    (void)sk_X509_push(chain, issuer);

    if (OCSP_basic_verify(basic, chain, NULL, OCSP_NOVERIFY) != 1)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "OCSP_basic_verify() failed")
            - errbuf;
        goto error;
    }

    id = OCSP_cert_to_id(NULL, cert, issuer);
    if (id == NULL)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "OCSP_cert_to_id() failed")
            - errbuf;
        goto error;
    }

    if (OCSP_resp_find_status(basic, id, &n, NULL, NULL, &thisupdate,
                              &nextupdate)
        != 1)
    {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "certificate status not found in the "
                                    "OCSP response")
                       - errbuf;
        goto error;
    }

    if (n != V_OCSP_CERTSTATUS_GOOD)
    {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "certificate status \"%s\" in the OCSP "
                                    "response",
                                    OCSP_cert_status_str(n))
                       - errbuf;
        goto error;
    }

    if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1)
    {
        *errbuf_size =
            ngx_snprintf(errbuf, *errbuf_size, "OCSP_check_validity() failed")
            - errbuf;
        goto error;
    }

    sk_X509_free(chain);
    X509_free(cert);
    X509_free(issuer);
    BIO_free(bio);
    OCSP_CERTID_free(id);
    OCSP_BASICRESP_free(basic);
    OCSP_RESPONSE_free(ocsp);

    return NGX_OK;

error:

    if (chain)
    {
        sk_X509_free(chain);
    }

    if (id)
    {
        OCSP_CERTID_free(id);
    }

    if (basic)
    {
        OCSP_BASICRESP_free(basic);
    }

    if (ocsp)
    {
        OCSP_RESPONSE_free(ocsp);
    }

    if (cert)
    {
        X509_free(cert);
    }

    if (issuer)
    {
        X509_free(issuer);
    }

    if (bio)
    {
        BIO_free(bio);
    }

    ERR_clear_error();

    return NGX_ERROR;

#endif /* NGX_HTTP_LUA_USE_OCSP */
}


#ifdef NGX_HTTP_LUA_USE_OCSP
static int
ngx_http_lua_ssl_empty_status_callback(ngx_ssl_conn_t *ssl_conn, void *data)
{
    return SSL_TLSEXT_ERR_OK;
}
#endif


int
ngx_http_lua_ffi_ssl_set_ocsp_status_resp(ngx_http_request_t *r,
                                          const u_char *resp, size_t resp_len,
                                          char **err)
{
#ifndef NGX_HTTP_LUA_USE_OCSP

    *err = "no OCSP support";
    return NGX_ERROR;

#else

    u_char *p;
    SSL_CTX *ctx;
    ngx_ssl_conn_t *ssl_conn;

    if (r->connection == NULL || r->connection->ssl == NULL)
    {
        *err = "bad request";
        return NGX_ERROR;
    }

    ssl_conn = r->connection->ssl->connection;
    if (ssl_conn == NULL)
    {
        *err = "bad ssl conn";
        return NGX_ERROR;
    }

#ifdef SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE
    if (SSL_get_tlsext_status_type(ssl_conn) == -1)
    {
#else
    if (ssl_conn->tlsext_status_type == -1)
    {
#endif
        dd("no ocsp status req from client");
        return NGX_DECLINED;
    }

    /* we have to register an empty status callback here otherwise
     * OpenSSL won't send the response staple. */

    ctx = SSL_get_SSL_CTX(ssl_conn);
    SSL_CTX_set_tlsext_status_cb(ctx, ngx_http_lua_ssl_empty_status_callback);

    p = OPENSSL_malloc(resp_len);
    if (p == NULL)
    {
        *err = "OPENSSL_malloc() failed";
        return NGX_ERROR;
    }

    ngx_memcpy(p, resp, resp_len);

    dd("set ocsp resp: resp_len=%d", (int)resp_len);
    (void)SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, resp_len);

    return NGX_OK;

#endif /* NGX_HTTP_LUA_USE_OCSP */
}

#endif /* NGX_LUA_NO_FFI_API */


#endif /* NGX_HTTP_SSL */
